package asteroids.model.programs;

import java.util.ArrayList;
import java.util.Map;
import java.util.Set;

import be.kuleuven.cs.som.annotate.*;

import asteroids.model.*;
import asteroids.model.programs.statement.*;
import asteroids.model.programs.type.*;
import asteroids.model.programs.expression.*;

public class Program {

	private Ship ship;
	private Map<String, T> globals;
	private S statement;
	private ShipType type;
	
	private double currentTime;
	
	public Program(Map<String, T> globals, S statement) {
		this.globals = globals;
		this.statement = statement;
	}
	
	@Basic
	public void setShip(Ship ship){
		this.ship = ship;
		this.type = new ShipType(ship);
		globals.put("self", type);
		validateVariables(statement);
	}
	
	/**
	 * Main method of the program, to execute it.
	 * @param time
	 * 		| the given time the program can execute.
	 */
	public void execute(double time){
		currentTime = currentTime + time;
			while(currentTime > 0.2){
				S nextStatement = statement.getNextStatement(); //the first non-executed statement
				S lastStatement = statement.getLastStatement(); //the last statement of the program
				executeStatement(nextStatement, type); //nextStatement always will execute
				if(nextStatement == lastStatement) //this means we are at the last statement of the program, 
					statement.setExecuted(false); // and have to restart from the beginning
				if(nextStatement.isAction())
					currentTime = currentTime - 0.2; //we can only execute 1 action each 0.2sec
			}
	}
	
	private void executeStatement(S s, Entity e){
		if(s == null) { } //should never happen
		else if(s.isForEach()){ 
			ForEach f = ((ForEach) s); //just for readability :)
			Set<SpaceObject> list = f.getList(ship.getWorld()); //get the spaceObjects to iterate over
			for(SpaceObject o : list){
				f.setVariableToEntity(f.getVariableExpressions(), new Entity(o)); //link the variableName with the spaceObject
				while(!f.getBody().hasBeenExecuted()){
					s = f.getBody().getNextStatement(); //execute the body
					executeStatement(s, new Entity(o));
				}
				f.getBody().setExecuted(false); //so we can re-run the body with the next spaceObject
			}
			f.setExecuted(true); //forEach and its body have been totally executed
		}
		else if(s.isAssignment())
			s.execute(searchType(((Assignment) s).getVariable())); //link with the right variable
		else 
			s.execute(e); // 'basic' statement (action, print etc.)
	}
	
	private T searchType(String name){
		return globals.get(name);
	}
	
	//link all variables with the right global variables
	private void validateVariables(S s){
		ArrayList<VariableExpression> variableList = s.getVariableExpressions();
		for(VariableExpression e: variableList){
			if(e != null)
				e.setValue(searchType(e.getName()));
		}
	}
	
	public String typecheck(){
		String returnString = "";
		setShip(new Ship());
		ArrayList<VariableExpression> variableList = statement.getVariableExpressions();
		for(VariableExpression e: variableList){
			if(e != null){
				if(searchType(e.getName())==null)
					returnString = returnString + "Undefined variable at line " + e.getLine() + ", column " + e.getColumn() + ".\n";
			}
		}
		return returnString + statement.typecheck(globals);
	}
}